{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "code",
      "source": [
        "import asyncio\n",
        "import aiohttp\n",
        "import time\n",
        "import json\n",
        "from typing import Dict, List, Optional, Any, Union\n",
        "from dataclasses import dataclass, asdict\n",
        "from datetime import datetime, timedelta\n",
        "import hashlib\n",
        "import logging\n",
        "\n",
        "!pip install aiohttp nest-asyncio"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "65lZFy3vKKrR",
        "outputId": "ae3faeae-9803-4264-89ed-e39a12286ad7"
      },
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Requirement already satisfied: aiohttp in /usr/local/lib/python3.11/dist-packages (3.11.15)\n",
            "Requirement already satisfied: nest-asyncio in /usr/local/lib/python3.11/dist-packages (1.6.0)\n",
            "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (2.6.1)\n",
            "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (1.3.2)\n",
            "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (25.3.0)\n",
            "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (1.6.0)\n",
            "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (6.4.4)\n",
            "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (0.3.1)\n",
            "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp) (1.20.0)\n",
            "Requirement already satisfied: idna>=2.0 in /usr/local/lib/python3.11/dist-packages (from yarl<2.0,>=1.17.0->aiohttp) (3.10)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "@dataclass\n",
        "class APIResponse:\n",
        "    \"\"\"Structured response object\"\"\"\n",
        "    data: Any\n",
        "    status_code: int\n",
        "    headers: Dict[str, str]\n",
        "    timestamp: datetime\n",
        "\n",
        "    def to_dict(self) -> Dict:\n",
        "        return asdict(self)"
      ],
      "metadata": {
        "id": "3OtkRPbnKMuZ"
      },
      "execution_count": 4,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "class RateLimiter:\n",
        "    \"\"\"Token bucket rate limiter\"\"\"\n",
        "    def __init__(self, max_calls: int = 100, time_window: int = 60):\n",
        "        self.max_calls = max_calls\n",
        "        self.time_window = time_window\n",
        "        self.calls = []\n",
        "\n",
        "    def can_proceed(self) -> bool:\n",
        "        now = time.time()\n",
        "        self.calls = [call_time for call_time in self.calls if now - call_time < self.time_window]\n",
        "\n",
        "        if len(self.calls) < self.max_calls:\n",
        "            self.calls.append(now)\n",
        "            return True\n",
        "        return False\n",
        "\n",
        "    def wait_time(self) -> float:\n",
        "        if not self.calls:\n",
        "            return 0\n",
        "        return max(0, self.time_window - (time.time() - self.calls[0]))"
      ],
      "metadata": {
        "id": "vcsIRR0pKNVo"
      },
      "execution_count": 5,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "class Cache:\n",
        "    \"\"\"Simple in-memory cache with TTL\"\"\"\n",
        "    def __init__(self, default_ttl: int = 300):\n",
        "        self.cache = {}\n",
        "        self.default_ttl = default_ttl\n",
        "\n",
        "    def _generate_key(self, method: str, url: str, params: Dict = None) -> str:\n",
        "        key_data = f\"{method}:{url}:{json.dumps(params or {}, sort_keys=True)}\"\n",
        "        return hashlib.md5(key_data.encode()).hexdigest()\n",
        "\n",
        "    def get(self, method: str, url: str, params: Dict = None) -> Optional[APIResponse]:\n",
        "        key = self._generate_key(method, url, params)\n",
        "        if key in self.cache:\n",
        "            response, expiry = self.cache[key]\n",
        "            if datetime.now() < expiry:\n",
        "                return response\n",
        "            del self.cache[key]\n",
        "        return None\n",
        "\n",
        "    def set(self, method: str, url: str, response: APIResponse, params: Dict = None, ttl: int = None):\n",
        "        key = self._generate_key(method, url, params)\n",
        "        expiry = datetime.now() + timedelta(seconds=ttl or self.default_ttl)\n",
        "        self.cache[key] = (response, expiry)"
      ],
      "metadata": {
        "id": "6YHjKvFBKPFR"
      },
      "execution_count": 6,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "class AdvancedSDK:\n",
        "    \"\"\"Advanced SDK with modern Python patterns\"\"\"\n",
        "\n",
        "    def __init__(self, base_url: str, api_key: str = None, rate_limit: int = 100):\n",
        "        self.base_url = base_url.rstrip('/')\n",
        "        self.api_key = api_key\n",
        "        self.session = None\n",
        "        self.rate_limiter = RateLimiter(max_calls=rate_limit)\n",
        "        self.cache = Cache()\n",
        "        self.logger = self._setup_logger()\n",
        "\n",
        "    def _setup_logger(self) -> logging.Logger:\n",
        "        logger = logging.getLogger(f\"SDK-{id(self)}\")\n",
        "        if not logger.handlers:\n",
        "            handler = logging.StreamHandler()\n",
        "            formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n",
        "            handler.setFormatter(formatter)\n",
        "            logger.addHandler(handler)\n",
        "            logger.setLevel(logging.INFO)\n",
        "        return logger\n",
        "\n",
        "    async def __aenter__(self):\n",
        "        \"\"\"Async context manager entry\"\"\"\n",
        "        self.session = aiohttp.ClientSession()\n",
        "        return self\n",
        "\n",
        "    async def __aexit__(self, exc_type, exc_val, exc_tb):\n",
        "        \"\"\"Async context manager exit\"\"\"\n",
        "        if self.session:\n",
        "            await self.session.close()\n",
        "\n",
        "    def _get_headers(self) -> Dict[str, str]:\n",
        "        headers = {'Content-Type': 'application/json'}\n",
        "        if self.api_key:\n",
        "            headers['Authorization'] = f'Bearer {self.api_key}'\n",
        "        return headers\n",
        "\n",
        "    async def _make_request(self, method: str, endpoint: str, params: Dict = None,\n",
        "                          data: Dict = None, use_cache: bool = True) -> APIResponse:\n",
        "        \"\"\"Core request method with rate limiting and caching\"\"\"\n",
        "\n",
        "        if use_cache and method.upper() == 'GET':\n",
        "            cached = self.cache.get(method, endpoint, params)\n",
        "            if cached:\n",
        "                self.logger.info(f\"Cache hit for {method} {endpoint}\")\n",
        "                return cached\n",
        "\n",
        "        if not self.rate_limiter.can_proceed():\n",
        "            wait_time = self.rate_limiter.wait_time()\n",
        "            self.logger.warning(f\"Rate limit hit, waiting {wait_time:.2f}s\")\n",
        "            await asyncio.sleep(wait_time)\n",
        "\n",
        "        url = f\"{self.base_url}/{endpoint.lstrip('/')}\"\n",
        "\n",
        "        try:\n",
        "            async with self.session.request(\n",
        "                method=method.upper(),\n",
        "                url=url,\n",
        "                params=params,\n",
        "                json=data,\n",
        "                headers=self._get_headers()\n",
        "            ) as resp:\n",
        "                response_data = await resp.json() if resp.content_type == 'application/json' else await resp.text()\n",
        "\n",
        "                api_response = APIResponse(\n",
        "                    data=response_data,\n",
        "                    status_code=resp.status,\n",
        "                    headers=dict(resp.headers),\n",
        "                    timestamp=datetime.now()\n",
        "                )\n",
        "\n",
        "                if use_cache and method.upper() == 'GET' and 200 <= resp.status < 300:\n",
        "                    self.cache.set(method, endpoint, api_response, params)\n",
        "\n",
        "                self.logger.info(f\"{method.upper()} {endpoint} - Status: {resp.status}\")\n",
        "                return api_response\n",
        "\n",
        "        except Exception as e:\n",
        "            self.logger.error(f\"Request failed: {str(e)}\")\n",
        "            raise\n",
        "\n",
        "    async def get(self, endpoint: str, params: Dict = None, use_cache: bool = True) -> APIResponse:\n",
        "        return await self._make_request('GET', endpoint, params=params, use_cache=use_cache)\n",
        "\n",
        "    async def post(self, endpoint: str, data: Dict = None) -> APIResponse:\n",
        "        return await self._make_request('POST', endpoint, data=data, use_cache=False)\n",
        "\n",
        "    async def put(self, endpoint: str, data: Dict = None) -> APIResponse:\n",
        "        return await self._make_request('PUT', endpoint, data=data, use_cache=False)\n",
        "\n",
        "    async def delete(self, endpoint: str) -> APIResponse:\n",
        "        return await self._make_request('DELETE', endpoint, use_cache=False)"
      ],
      "metadata": {
        "id": "XZc6P-AqKQ3Z"
      },
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "async def demo_sdk():\n",
        "    \"\"\"Demonstrate SDK capabilities\"\"\"\n",
        "    print(\"🚀 Advanced SDK Demo\")\n",
        "    print(\"=\" * 50)\n",
        "\n",
        "    async with AdvancedSDK(\"https://jsonplaceholder.typicode.com\") as sdk:\n",
        "\n",
        "        print(\"\\n📥 Testing GET request with caching...\")\n",
        "        response1 = await sdk.get(\"/posts/1\")\n",
        "        print(f\"First request - Status: {response1.status_code}\")\n",
        "        print(f\"Title: {response1.data.get('title', 'N/A')}\")\n",
        "\n",
        "        response2 = await sdk.get(\"/posts/1\")\n",
        "        print(f\"Second request (cached) - Status: {response2.status_code}\")\n",
        "\n",
        "        print(\"\\n📤 Testing POST request...\")\n",
        "        new_post = {\n",
        "            \"title\": \"Advanced SDK Tutorial\",\n",
        "            \"body\": \"This SDK demonstrates modern Python patterns\",\n",
        "            \"userId\": 1\n",
        "        }\n",
        "        post_response = await sdk.post(\"/posts\", data=new_post)\n",
        "        print(f\"POST Status: {post_response.status_code}\")\n",
        "        print(f\"Created post ID: {post_response.data.get('id', 'N/A')}\")\n",
        "\n",
        "        print(\"\\n⚡ Testing batch requests with rate limiting...\")\n",
        "        tasks = []\n",
        "        for i in range(1, 6):\n",
        "            tasks.append(sdk.get(f\"/posts/{i}\"))\n",
        "\n",
        "        results = await asyncio.gather(*tasks)\n",
        "        print(f\"Batch completed: {len(results)} requests\")\n",
        "        for i, result in enumerate(results, 1):\n",
        "            print(f\"  Post {i}: {result.data.get('title', 'N/A')[:30]}...\")\n",
        "\n",
        "        print(\"\\n❌ Testing error handling...\")\n",
        "        try:\n",
        "            error_response = await sdk.get(\"/posts/999999\")\n",
        "            print(f\"Error response status: {error_response.status_code}\")\n",
        "        except Exception as e:\n",
        "            print(f\"Handled error: {type(e).__name__}\")\n",
        "\n",
        "    print(\"\\n✅ Demo completed successfully!\")\n",
        "\n",
        "async def run_demo():\n",
        "  \"\"\"Colab-friendly demo runner\"\"\"\n",
        "  await demo_sdk()\n"
      ],
      "metadata": {
        "id": "wChz_ciUKVPb"
      },
      "execution_count": 8,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cqfJ8Nf0JNiK",
        "outputId": "912933fa-a200-4f1a-b4b9-def7b826e99a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2025-06-14 15:58:31,396 - SDK-132626697886800 - INFO - GET /posts/1 - Status: 200\n",
            "INFO:SDK-132626697886800:GET /posts/1 - Status: 200\n",
            "2025-06-14 15:58:31,398 - SDK-132626697886800 - INFO - Cache hit for GET /posts/1\n",
            "INFO:SDK-132626697886800:Cache hit for GET /posts/1\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "🚀 Advanced SDK Demo\n",
            "==================================================\n",
            "\n",
            "📥 Testing GET request with caching...\n",
            "First request - Status: 200\n",
            "Title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit\n",
            "Second request (cached) - Status: 200\n",
            "\n",
            "📤 Testing POST request...\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "2025-06-14 15:58:31,639 - SDK-132626697886800 - INFO - POST /posts - Status: 201\n",
            "INFO:SDK-132626697886800:POST /posts - Status: 201\n",
            "2025-06-14 15:58:31,642 - SDK-132626697886800 - INFO - Cache hit for GET /posts/1\n",
            "INFO:SDK-132626697886800:Cache hit for GET /posts/1\n",
            "2025-06-14 15:58:31,668 - SDK-132626697886800 - INFO - GET /posts/2 - Status: 200\n",
            "INFO:SDK-132626697886800:GET /posts/2 - Status: 200\n",
            "2025-06-14 15:58:31,707 - SDK-132626697886800 - INFO - GET /posts/3 - Status: 200\n",
            "INFO:SDK-132626697886800:GET /posts/3 - Status: 200\n",
            "2025-06-14 15:58:31,713 - SDK-132626697886800 - INFO - GET /posts/5 - Status: 200\n",
            "INFO:SDK-132626697886800:GET /posts/5 - Status: 200\n",
            "2025-06-14 15:58:31,719 - SDK-132626697886800 - INFO - GET /posts/4 - Status: 200\n",
            "INFO:SDK-132626697886800:GET /posts/4 - Status: 200\n",
            "2025-06-14 15:58:31,746 - SDK-132626697886800 - INFO - GET /posts/999999 - Status: 404\n",
            "INFO:SDK-132626697886800:GET /posts/999999 - Status: 404\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "POST Status: 201\n",
            "Created post ID: 101\n",
            "\n",
            "⚡ Testing batch requests with rate limiting...\n",
            "Batch completed: 5 requests\n",
            "  Post 1: sunt aut facere repellat provi...\n",
            "  Post 2: qui est esse...\n",
            "  Post 3: ea molestias quasi exercitatio...\n",
            "  Post 4: eum et est occaecati...\n",
            "  Post 5: nesciunt quas odio...\n",
            "\n",
            "❌ Testing error handling...\n",
            "Error response status: 404\n",
            "\n",
            "✅ Demo completed successfully!\n"
          ]
        }
      ],
      "source": [
        "import nest_asyncio\n",
        "nest_asyncio.apply()\n",
        "\n",
        "if __name__ == \"__main__\":\n",
        "    try:\n",
        "        asyncio.run(demo_sdk())\n",
        "    except RuntimeError:\n",
        "        loop = asyncio.get_event_loop()\n",
        "        loop.run_until_complete(demo_sdk())\n",
        "\n",
        "class SDKBuilder:\n",
        "    \"\"\"Builder pattern for SDK configuration\"\"\"\n",
        "    def __init__(self, base_url: str):\n",
        "        self.base_url = base_url\n",
        "        self.config = {}\n",
        "\n",
        "    def with_auth(self, api_key: str):\n",
        "        self.config['api_key'] = api_key\n",
        "        return self\n",
        "\n",
        "    def with_rate_limit(self, calls_per_minute: int):\n",
        "        self.config['rate_limit'] = calls_per_minute\n",
        "        return self\n",
        "\n",
        "    def build(self) -> AdvancedSDK:\n",
        "        return AdvancedSDK(self.base_url, **self.config)"
      ]
    }
  ]
}